/**
 * This is the file for change tracker. The change tracker is the mixin for dataset. It can track
 * the change of the dataset. Provide the change loggin ability for dataset.
 * 
 * @since 1/5/2008
 * @version 1.0
 * @author Jack
 */

dojo.provide("spaghetti.dataset.changeTracker");

dojo.require("spaghetti.utility");
dojo.require("spaghetti.dataset.api");

dojo.declare("spaghetti.dataset.Change", null, {
	//	summary:
	//		The log entry for change track. The log entry has tree types(add, remove, modified). 
	//		The log entry hold the reference of the changed record. It can have some metadata, the
	//		metadata is different for different type. If the change type is add, the metadata is
	//		null. But if the change type is modified, matadata has the oldvalue, newvalue.
	
    constructor: function(/*Record*/record, /*String*/ type, /*Object*/ meta){
        this.record = record;
        this.type = type;
        if (!spaghetti.isNull(meta)) {
            dojo.mixin(this, meta);
        }
    }
});

dojo.declare("spaghetti.dataset.ChangeTracker", null, {
    //	summary:
    //		The mixin used for tracking the change in the dataset. The changes is tracked in an
    //		object, its structure is like this:
    //		|	{
    //		|		record_code : {
    //		|			add : add_change,
    //		|			remove : remove_change,
    //		|			change : {
    //		|				property_name : {
    //		|					first : modify_change,
    //		|					last : modify_change
    //		|				}
    //		|			}
    //		|		}
    //		|	}
    //
    //		This structure only support one step undo(last change). It is not a undo manager. But
    //		of course, you can mixin a undo manager to a dataset. That's the reason why not define
    //		the track change api into dataset. Because the undomanager is more complex than a
    //		tracker. It doesn't use cache to store the changes either. Keep the tracker small, 
    //		maybe is a good choice.
    
    //	logging: Boolean
    //		The logging switch for tracking. If it is on, all the change in the dataset will be tracked.
    logging: true,
    
    initTrack: function(){
        //	summary:
        //		Initialize the tracker. The tracker can track the change on the dataset. This
        //		tracker must be mixed into a dataset implementation to do the track.
		
        this.addTrack = dojo.connect(this, "onRecordAdd", this, "onAdd");
        this.removeTrack = dojo.connect(this, "onRecordRemove", this, "onRemove");
        this.changeTrack = dojo.connect(this, "onRecordChange", this, "onChange");
        this.recordCode = 0;
    },
    
    /*Object*/
    getAllChanges: function(){
        //	summary:
        //		Get all the changes the tracker tracked. You can do every thing(even evil) to
        //		it, The structure is discussed above.
        
        if (spaghetti.isNull(this.changes)) {
            this.changes = {};
        }
        return this.changes;
    },
    
    /*Object*/
    getChanges: function(record){
        //	summary:
        //		Get the tracked changes for the record.
        
        if (spaghetti.isNull(this.getAllChanges()[record._record_code])) {
            this.getAllChanges()[record._record_code] = {};
        }
        return this.getAllChanges()[record._record_code];
    },
    
    onAdd: function(/*Record*/record, /*Integer*/ index){
        // Set the record code to the record. The record code is the key to identify the record
        // for this tracker.
        record._record_code = this.recordCode;
        this.recordCode++;
        
        if (this.logging) 
            this.getChanges(record).add = new spaghetti.dataset.Change(record, "add", {
                index: index
            });
    },
    
    _setChange: function(/*Changes*/changes, /*Modify Change*/ change){
        //	summary:
        //		Use the simple strategy(first, last) to track the change. Maybe overwrite.
        
        // The property that changed.
        var prop = change.property;
        
        // If the property never chagned, init for it.
        if (spaghetti.isNull(changes.change)) {
            changes.change = {};
        }
        
        // Take all the changes for this property
        var cs = changes.change[prop];
		if(spaghetti.isNull(changes.change[prop]))
			cs = changes.change[prop] = {};
        
        // If is the first time it changed, save the change at the first. It'll never change. If
        // is not the first time, rewrite the last change.
        if (spaghetti.isNull(cs.first)) 
            cs.first = change;
        else 
            cs.last = change;
    },
    
    onChange: function(/*Record*/record, /*String*/ property, /*Object*/ oldValue, /*Object*/ newValue){
        if (this.logging) {
            this._setChange(this.getChanges(record), new spaghetti.dataset.Change(record, "modify", {
                property: property,
                oldValue: oldValue,
                newValue: newValue
            }));
        }
    },
    
    onRemove: function(/*Record*/record){
        if (this.logging) 
            this.getChanges(record).remove = new spaghetti.dataset.Change(record, "remove");
    },
    
    clearChanges: function(){
        //	summary:
        //		Clear all the tracked changes, can not be undo.
        
        this.changes = null;
    },
    
    /*Boolean*/
    isRecordAdded: function(/*Record*/record){
        return spaghetti.isNull(this.getChanges(record).add);
    },
    
    /*Boolean*/
    isRecordRemoved: function(/*Record*/record){
        return spaghetti.isNull(this.getChanges(record).remove);
    },
    
    /*Boolean*/
    isRecordChanged: function(/*Record*/record){
        return spaghetti.isNull(this.getChanges(record).change);
    }
});
